package com.jcompressor.faces.annotations;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.faces.application.ResourceDependencies;
import javax.faces.application.ResourceDependency;
import javax.faces.component.UIComponent;

import org.apache.commons.lang.StringUtils;
import org.reflections.Reflections;
import org.reflections.scanners.TypeAnnotationsScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jcompressor.JcompressorContext;
import com.jcompressor.annotations.AnnotationScanner;
import com.jcompressor.annotations.JcompressorResource;
import com.jcompressor.annotations.JcompressorResources;
import com.jcompressor.model.config.Configuration;
import com.jcompressor.model.config.Resource;
import com.jcompressor.model.config.Scan;

public class FacesAnnotationScanner implements AnnotationScanner {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(FacesAnnotationScanner.class);
	
	private List<UIComponent> components;
	private Class<? extends Annotation>[] annotations;
	private List<JcompressorResource> jcompressorResources;
	private List<ResourceDependency> facesResources;
	
	/**
	 * The default constructor
	 * @param annotations the annotations
	 */
	public FacesAnnotationScanner(final Class<? extends Annotation>... annotations) {
		this.annotations = annotations;
		this.jcompressorResources = new ArrayList<JcompressorResource>();
		this.facesResources = new ArrayList<ResourceDependency>();
	}
	
	public FacesAnnotationScanner(final List<UIComponent> components, final Class<? extends Annotation>... annotations) {
		this(annotations);
		this.components = components;
	}
	
	@Override
	public void scan() {
		if (this.components == null) {
			for (final Scan scan : JcompressorContext.getConfigurator().getConfiguration().getBuild().getScan()) {
				final ConfigurationBuilder configBuilder = new ConfigurationBuilder();
				configBuilder.setUrls(ClasspathHelper.forPackage(scan.getValue()));
				configBuilder.setScanners(new TypeAnnotationsScanner());
				
				final Reflections reflections = new Reflections(configBuilder);
				for (final Class<? extends Annotation> annotatedClass : this.annotations) {
					final Set<Class<?>> annotated = reflections.getTypesAnnotatedWith(annotatedClass);
					this.handleAnnotatedClasses(annotated);
				}
			}
		}
		else {
			final Set<Class<?>> classes = new HashSet<Class<?>>();
			for (final UIComponent component : this.components) {
				classes.add(component.getClass());
			}
			
			this.handleAnnotatedClasses(classes);
		}
	}
	
	private void handleAnnotatedClasses(final Set<Class<?>> annotated) {
		for (final Class<?> clazz : annotated) {
			for (final Annotation annotation : clazz.getAnnotations()) {
				if (JcompressorResource.class.isAssignableFrom(annotation.annotationType())) {
					final JcompressorResource resource = (JcompressorResource)annotation;
					if (!this.jcompressorResources.contains(resource)) {
						LOGGER.debug("Registering new resource " + resource.toString());
						this.jcompressorResources.add(resource);
					}
				}
				else if (JcompressorResources.class.isAssignableFrom(annotation.annotationType())) {
					for (final JcompressorResource resource : ((JcompressorResources)annotation).value()) {
						if (!this.jcompressorResources.contains(resource)) {
							LOGGER.debug("Registering new resource " + resource.toString());
							this.jcompressorResources.add(resource);
						}
					}
				}
				else if (ResourceDependency.class.isAssignableFrom(annotation.annotationType())) {
					final ResourceDependency resource = (ResourceDependency)annotation;
					if (!this.facesResources.contains(resource)) {
						LOGGER.debug("Registering new faces resource " + resource.toString());
						this.facesResources.add(resource);
					}
				}
				else if (ResourceDependencies.class.isAssignableFrom(annotation.annotationType())) {
					for (final ResourceDependency resource : ((ResourceDependencies)annotation).value()) {
						if (!this.facesResources.contains(resource)) {
							LOGGER.debug("Registering new faces resource " + resource.toString());
							this.facesResources.add(resource);
						}
					}
				}
			}
		}
	}
	
	public List<Resource> getResources() {
		final Configuration config = JcompressorContext.getConfigurator().getConfiguration();
		final List<Resource> resources = new ArrayList<Resource>();
		
		for (final ResourceDependency annotatedResource : this.facesResources) {
			final Resource resource = new Resource();
			resource.setLibrary(annotatedResource.library());
			resource.setName(annotatedResource.name());
			resources.add(resource);
		}
		
		for (final JcompressorResource annotatedResource : this.jcompressorResources) {
			final Resource storedResource = config.getResource(annotatedResource.id());
			if (storedResource == null) {
				LOGGER.error("Resource '" + annotatedResource.id() + "' not found");
			}
			else {
				final Resource resource = new Resource();
				resource.setId(annotatedResource.id());
				
				if (StringUtils.isBlank(annotatedResource.library())) {
					resource.setLibrary(storedResource.getLibrary());
				}
				else {
					resource.setLibrary(annotatedResource.library());
				}
				
				if (StringUtils.isBlank(annotatedResource.name())) {
					resource.setName(storedResource.getName());
				}
				else {
					resource.setName(annotatedResource.name());
				}
				
				if (StringUtils.isBlank(annotatedResource.target())) {
					resource.setTarget(storedResource.getTarget());
				}
				else {
					resource.setTarget(annotatedResource.target());
				}
				
				resources.add(resource);
			}
		}
		
		return resources;
	}
	
	public List<JcompressorResource> getJcompressorResources() {
		return this.jcompressorResources;
	}
	
	public List<ResourceDependency> getFacesResources() {
		return this.facesResources;
	}
	
	public void setJcompressorResources(final List<JcompressorResource> jcompressorResources) {
		this.jcompressorResources = jcompressorResources;
	}
	
	public void setFacesResources(final List<ResourceDependency> facesResources) {
		this.facesResources = facesResources;
	}
	
}